/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.U.
 */
package es.silenus.detecta.client;

import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;


/**
 * Jasig CAS authenticator.
 *
 * @author <a href="mailto:malonso@silenus-consultoria.es">Mariano Alonso</a>
 *
 * @since 12-ago-2009 10:18:19
 */
public class CasFormAuthenticator extends AbstractFormAuthenticator {
	/**
	 * Logger for this class.
	 */
	private static final Log LOG = LogFactory.getLog(CasFormAuthenticator.class);

	/**
	 * Quot.
	 */
	private static final String QUOT = "\"";

	/**
	 * CAS Spring Flow token.
	 */
	private static final String CAS_SPRING_FLOW = "<input type=\"hidden\" name=\"lt\" value=\"";

	/**
	 * CAS Form action which has encoded service access.
	 */
	private static final String CAS_FORM_ACTION = "<form id=\"fm1\" class=\"fm-v clearfix\" action=\"";

	/**
	 * CAS ticket cookie name.
	 */
	private static final String CAS_COOKIE = "CASTGC";

	/**
	 * DETECTA User json start.
	 */
	private static final String DETECTA_USER_START = "var USER = {";

	/**
	 * DETECTA User json end.
	 */
	private static final String DETECTA_USER_END = "};";

	/**
	 * CAS Success start token.
	 */
	private static final String CAS_SUCCESS_START = "<div id=\"msg\" class=\"success\">";

	/**
	 * CAS Success end token.
	 */
	private static final String CAS_SUCCESS_END = "</div>";

	/**
	 * Retrieves the spring flow key.
	 *
	 * @param response the response.
	 *
	 * @return the spring flow key.
	 */
	private String getSpringFlow(final String response) {
		return getResponseValue(response, CAS_SPRING_FLOW, QUOT);
	}

	/**
	 * Retrieves the cas login form action.
	 *
	 * @param response the response.
	 *
	 * @return the cas login form action.
	 */
	private String getFormAction(final String response) {
		return getResponseValue(response, CAS_FORM_ACTION, QUOT);
	}

	/**
	 * Retrieves the detecta user json.
	 *
	 * @param response the response.
	 *
	 * @return the detecta user json.
	 */
	private String getDetectaUser(final String response) {
		return getResponseValue(response, DETECTA_USER_START, DETECTA_USER_END);
	}

	/**
	 * Retrieves the logout success token.
	 *
	 * @param response the response.
	 *
	 * @return the logout success token.
	 */
	private String getLogoutSuccess(final String response) {
		return getResponseValue(response, CAS_SUCCESS_START, CAS_SUCCESS_END);
	}

	/**
	 * Performs the user login.
	 *
	 * @throws IOException if an I/O error occurs.
	 * @throws RuntimeException if something goes wrong.
	 */
	public void login() throws IOException {
		LOG.info("Performing log in...");

		final String loginURL = getURL(configuration.getString("login.uri", null));

		int status;

		// Access login page
		GetMethod ticketMethod = new GetMethod(loginURL);

		ticketMethod.setFollowRedirects(true);

		String springFlowTicket;
		String formAction;

		try {
			client.executeMethod(ticketMethod);

			if(LOG.isDebugEnabled()) {
				LOG.debug("Accessed login page: " + formatMethodResponse(ticketMethod));
				LOG.debug("Login page cookies: " + printCookies());
			}

			final String ticketResponse = ticketMethod.getResponseBodyAsString();

			springFlowTicket = getSpringFlow(ticketResponse);
			formAction = getFormAction(ticketResponse);
		} finally {
			ticketMethod.releaseConnection();
		}

		if((springFlowTicket == null) || (formAction == null)) {
			throw new RuntimeException("Wrong authenticator has been chosen or the server pages have changed");
		}

		// Do login
		PostMethod loginMethod = new PostMethod(getURL(formAction));

		loginMethod.addParameter("username", getUserName());
		loginMethod.addParameter("password", getUserPassword());

		loginMethod.addParameter("_eventId", "submit");
		loginMethod.addParameter("submit", "submit");

		loginMethod.addParameter("lt", springFlowTicket);

		try {
			status = client.executeMethod(loginMethod);

			if(LOG.isDebugEnabled()) {
				LOG.debug("Login response: " + formatMethodResponse(loginMethod));
				LOG.debug("Login response cookies: " + printCookies());
			}
		} finally {
			loginMethod.releaseConnection();
		}

		String cookie = getCookie(CAS_COOKIE);

		if((status != 302) || (cookie == null)) {
			throw new RuntimeException("Invalid authentication, CAS cookie not found");
		}


		// Follow redirection to validate the CAS ticket
		String detectaLoginURL = loginMethod.getResponseHeader("Location").getValue();

		ticketMethod = new GetMethod(detectaLoginURL);
		ticketMethod.setFollowRedirects(true);

		try {
			client.executeMethod(ticketMethod);

			if(LOG.isDebugEnabled()) {
				LOG.debug("Validate ticket response: " + formatMethodResponse(ticketMethod));
				LOG.debug("Validate ticket response cookies: " + printCookies());
			}


			String detectaUser = getDetectaUser(ticketMethod.getResponseBodyAsString());

			if(detectaUser == null) {
				throw new RuntimeException("Could not validate CAS ticket in DETECTA");
			}
		} finally {
			ticketMethod.releaseConnection();
		}
	}

	/**
	 * Performs the user logout.
	 *
	 * @throws IOException if an I/O error occurs.
	 * @throws RuntimeException if something goes wrong.
	 */
	public void logout() throws IOException {
		LOG.info("Performing log out...");

		String logoutURL = getURL(configuration.getString("logout.uri", null));

		GetMethod logoutMethod = null;

		try {
			logoutMethod = new GetMethod(logoutURL);

			client.executeMethod(logoutMethod);

			if(LOG.isDebugEnabled()) {
				LOG.debug("Logout response: " + formatMethodResponse(logoutMethod));
				LOG.debug("Logout response cookies: " + printCookies());
			}

			String logoutSuccess = getLogoutSuccess(logoutMethod.getResponseBodyAsString());

			if(logoutSuccess == null) {
				throw new RuntimeException("Logout did not work, please check the configuration");
			}
		} finally {
			if(logoutMethod != null) {
				logoutMethod.releaseConnection();
			}
		}
	}
}
